package com.ysp.summary.controller;

import cn.hutool.core.util.RandomUtil;
import com.google.common.base.Stopwatch;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.ysp.summary.entity.RespResult;
import com.ysp.summary.entity.TestPublisherEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 *  异步多线程 编写接口
 * 参考资料 https://www.toutiao.com/i6783214804937998851/
 */
@Slf4j
@RestController
@RequestMapping("multi-thread")
public class MultiThreadCode {

    /**
     * 计时器
     */
    public static final Stopwatch time = Stopwatch.createUnstarted();

    @Resource
    private ThreadPoolTaskExecutor executor;

    /**
     * 异步广播事件  适合解耦合
     */
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    public static final String EVENTPREFIX = "EVENT-202106";

    /**
     * 同步阻塞执行业务逻辑    我们平时的大部分编码风格
     * @return
     */
    @RequestMapping(value = "testEvent",method = {RequestMethod.GET,RequestMethod.POST})
    public RespResult testEvent(Integer type,String eventNo){

        time.start();
        TestPublisherEvent event = new TestPublisherEvent();
        event.setId(RandomUtil.randomInt());
        event.setName("异步发布事件的名称");
        event.setType(1);
        event.setEventNo(EVENTPREFIX+RandomUtil.randomNumbers(5));
        applicationEventPublisher.publishEvent(event);
        System.out.println("广播是异步执行还是同步执行了，当前线程是： "+Thread.currentThread().getName());
        time.stop();
        time.reset();
        return RespResult.success();
    }


    /**
     * 同步阻塞执行业务逻辑    我们平时的大部分编码风格
     * @return
     */
    @RequestMapping("singelThread")
    public RespResult<List<String>> singelThread(){
        time.start();
        List<String> list = new ArrayList<>(8);
        // 模拟查询接口的 数据组装 假设调用四个业务方法才返回数据
        list.addAll(task());
        list.addAll(task());
        list.addAll(task());
        list.addAll(task());
        time.stop();
        log.info("模拟单线程同步执行业务耗时：{}毫秒",time.elapsed(TimeUnit.MILLISECONDS));
        time.reset();
        return RespResult.success(list);
    }

    /**
     * Future 表示一个线程异步执行任务后的结果  并且future.get() 是阻塞调用的 要想拿到线程的执行结果 必须是future.get()阻塞
     * 或者while（future.isDone()）轮询方式调用   这种也叫 主动拉取（Pull） 现在都流行响应式编程，即主动推（Push）的方式，当线程执行
     * 完毕 你告诉我好了
     *
     *  线程池 + future 实现异步执行任务
     * @return
     */
    @RequestMapping("asyncFuture")
    public RespResult<List<List<String>>> asyncFuture(){
        time.start();
        // 模拟查询接口的 数据组装 假设调用四个业务方法才返回数据
        Future<List<String>>  future1 = executor.submit(this::task);
        Future<List<String>>  future2 = executor.submit(this::task);
        Future<List<String>>  future3 = executor.submit(this::task);
        Future<List<String>>  future4 = executor.submit(this::task);
        List<Future<List<String>>> futureList = Arrays.asList(future1,future2,future3,future4);
        List<List<String>> list1 = futureList.stream().map(future ->{
            try {
               return future.get();
            }catch (Exception e){
                return null;
            }
        }).collect(Collectors.toList());
        time.stop();
        log.info("模拟单线程同步执行业务耗时：{}毫秒",time.elapsed(TimeUnit.MILLISECONDS));
        time.reset();
        return RespResult.success(list1);
    }

    /**
     *  Java8 的 completableFuture  对future的一个增强
     * @return
     */
    @RequestMapping("asyncCompletableFuture")
    public RespResult<List<List<String>>> asyncCompletableFuture(){
        time.start();
        List<CompletableFuture<List<String>>> futureList = new ArrayList<>(8);
        // 模拟查询接口的 数据组装 假设调用四个业务方法才返回数据
        futureList.add(CompletableFuture.supplyAsync(this::task,executor));
        futureList.add(CompletableFuture.supplyAsync(this::task,executor));
        futureList.add(CompletableFuture.supplyAsync(this::task,executor));
        futureList.add(CompletableFuture.supplyAsync(this::task,executor));
        CompletableFuture<Void> allFuture =CompletableFuture.allOf(futureList.toArray(new CompletableFuture[4]));
        CompletableFuture<List<List<String>>> listCompletableFuture = allFuture.thenApplyAsync(
                var ->futureList.stream().map(CompletableFuture ::join).collect(Collectors.toList()),executor
        );
        List<List<String>> list = listCompletableFuture.join();
        time.stop();
        log.info("模拟单线程同步执行业务耗时：{}毫秒",time.elapsed(TimeUnit.MILLISECONDS));
        time.reset();
        return RespResult.success(list);
    }

    /**
     *  Guava 的 ListenableFuture
     *
     *  牛逼的公司牛逼的人出一些牛逼的开源组件要比官方自带的工具类要好得多，同样，
     *  谷歌开源的Guava中的ListenableFuture接口对java自带的Future接口做了进一步拓展，并且提供了静态工具类Futures
     *
     *  与之前不同的是，Guava中需要对线程池再进行一次包装
     * @return
     */
    @RequestMapping("asyncGuava")
    public RespResult<List<List<String>>> asyncGuava() throws ExecutionException, InterruptedException {
        time.start();

        /**
         * 包装线程池
         */
        ListeningExecutorService guavaExecutor = MoreExecutors.listeningDecorator(executor.getThreadPoolExecutor());

        ListenableFuture<List<String>> lf1 = guavaExecutor.submit(this::task);
        ListenableFuture<List<String>> lf2 = guavaExecutor.submit(this::task);
        ListenableFuture<List<String>> lf3 = guavaExecutor.submit(this::task);
        ListenableFuture<List<String>> lf4 = guavaExecutor.submit(this::task);

        ListenableFuture<List<List<String>>> listListenableFuture = Futures.allAsList(lf1,lf2,lf3,lf4);
        List<List<String>> list = listListenableFuture.get();
        time.stop();
        log.info("模拟单线程同步执行业务耗时：{}毫秒",time.elapsed(TimeUnit.MILLISECONDS));
        time.reset();
        return RespResult.success(list);
    }


    private List<String> task(){
        List<String> list = new ArrayList<>(500000);
        for (int i = 0; i < 500000; i++) {
            list.add("ysp test");
        }
        try {
            Thread.sleep(150);
            log.info("当前执行此代码的线程名称：coreSize:{}, {} ",executor.getCorePoolSize(),Thread.currentThread().getName());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return list;
    }
}
